Spring boot整合篇 [TOC]
1.优秀传送门 1-1.第一章送给GitHub仓库,多种框架整合实战案例 https://github.com/JeffLi1993/springboot-learning-example
公告 该套框架整合尽量以一个工程进行叠加演示,一般情况下后面的整合都包含前面的基础整合
(上面 1 仓库实战是各个框架分离,可以参考学习)
2.基础整合搭建ZK/Dubbo Dubbo Spring Boot Starter 致力于简化 Dubbo 应用在 Spring Boot 环境中的开发,主要包括自动装配(Auto-Configure)、外部化配置(Externalized-Configuration)以及生产准备(Actuator)
思路
在Spring boot中整合ZK/Dubbo,可以将ZK抽出来放在parent中,这样子模块只需要考虑dubbo的服务配置即可
利用强大的注解功能,运用Dubbo的@Service做服务端注解,@Reference做消费端注解,快速整合Springboot+ZK+Dubbo
2-1.基础搭建传送门 https://www.bysocket.com/?p=1681
2-2.优秀开源项目 https://github.com/dubbo/dubbo-spring-boot-project
Springboot 多模块项目,整合了freemark,jsp,logback,mail,多数据源,mybatis,redis,docker,SSL等(待验证)
https://github.com/dony15/springboot-dubbox
2-3.配置详解 application.properties
简单基础配置
1 2 3 4 5 6 ## Dubbo 服务提供者配置 spring.dubbo.application.name=provider/consumer spring.dubbo.registry.address=zookeeper://127.0.0.1:2181 spring.dubbo.protocol.name=dubbo spring.dubbo.protocol.port=20880 spring.dubbo.scan=org.spring.springboot.
spring.dubbo.application.name 应用名称
spring.dubbo.registry.address 注册中心地址
spring.dubbo.protocol.name 协议名称
spring.dubbo.protocol.port 协议端口
spring.dubbo.scan dubbo 服务类包目录
详细配置清单
#根据 starter 工程源码,可以看出 application.properties 对应的 Dubbo 配置类 DubboProperties 。
1 2 3 4 5 6 7 8 9 10 11 12 13 @ConfigurationProperties(prefix = "spring.dubbo") public class DubboProperties { private String scan; private ApplicationConfig application; private RegistryConfig registry; private ProtocolConfig protocol; }
包括了扫描路径、应用配置类、注册中心配置类和服务协议类 所以具体常用配置下扫描包路径:指的是 Dubbo 服务注解的服务包路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 ## Dubbo 配置 # 扫描包路径 spring.dubbo.scan=org.spring.springboot.dubbo 应用配置类:关于 Dubbo 应用级别的配置 ## Dubbo 应用配置 # 应用名称 spring.dubbo.application.name=xxx # 模块版本 spring.dubbo.application.version=xxx # 应用负责人 spring.dubbo.application.owner=xxx # 组织名(BU或部门) spring.dubbo.application.organization=xxx # 分层 spring.dubbo.application.architecture=xxx # 环境,如:dev/test/run spring.dubbo.application.environment=xxx # Java代码编译器 spring.dubbo.application.compiler=xxx # 日志输出方式 spring.dubbo.application.logger=xxx # 注册中心 0 spring.dubbo.application.registries[0].address=zookeeper:#127.0.0.1:2181=xxx # 注册中心 1 spring.dubbo.application.registries[1].address=zookeeper:#127.0.0.1:2181=xxx # 服务监控 spring.dubbo.application.monitor.address=xxx 这里注意多个注册中心的配置方式。下面介绍单个注册中心的配置方式。 注册中心配置类:常用 ZooKeeper 作为注册中心进行服务注册。 ## Dubbo 注册中心配置类 # 注册中心地址 spring.dubbo.application.registries.address=xxx # 注册中心登录用户名 spring.dubbo.application.registries.username=xxx # 注册中心登录密码 spring.dubbo.application.registries.password=xxx # 注册中心缺省端口 spring.dubbo.application.registries.port=xxx # 注册中心协议 spring.dubbo.application.registries.protocol=xxx # 客户端实现 spring.dubbo.application.registries.transporter=xxx spring.dubbo.application.registries.server=xxx spring.dubbo.application.registries.client=xxx spring.dubbo.application.registries.cluster=xxx spring.dubbo.application.registries.group=xxx spring.dubbo.application.registries.version=xxx # 注册中心请求超时时间(毫秒) spring.dubbo.application.registries.timeout=xxx # 注册中心会话超时时间(毫秒) spring.dubbo.application.registries.session=xxx # 动态注册中心列表存储文件 spring.dubbo.application.registries.file=xxx # 停止时等候完成通知时间 spring.dubbo.application.registries.wait=xxx # 启动时检查注册中心是否存在 spring.dubbo.application.registries.check=xxx # 在该注册中心上注册是动态的还是静态的服务 spring.dubbo.application.registries.dynamic=xxx # 在该注册中心上服务是否暴露 spring.dubbo.application.registries.register=xxx # 在该注册中心上服务是否引用 spring.dubbo.application.registries.subscribe=xxx 服务协议配置类: ## Dubbo 服务协议配置 # 服务协议 spring.dubbo.application.protocol.name=xxx # 服务IP地址(多网卡时使用) spring.dubbo.application.protocol.host=xxx # 服务端口 spring.dubbo.application.protocol.port=xxx # 上下文路径 spring.dubbo.application.protocol.contextpath=xxx # 线程池类型 spring.dubbo.application.protocol.threadpool=xxx # 线程池大小(固定大小) spring.dubbo.application.protocol.threads=xxx # IO线程池大小(固定大小) spring.dubbo.application.protocol.iothreads=xxx # 线程池队列大小 spring.dubbo.application.protocol.queues=xxx # 最大接收连接数 spring.dubbo.application.protocol.accepts=xxx # 协议编码 spring.dubbo.application.protocol.codec=xxx # 序列化方式 spring.dubbo.application.protocol.serialization=xxx # 字符集 spring.dubbo.application.protocol.charset=xxx # 最大请求数据长度 spring.dubbo.application.protocol.payload=xxx # 缓存区大小 spring.dubbo.application.protocol.buffer=xxx # 心跳间隔 spring.dubbo.application.protocol.heartbeat=xxx # 访问日志 spring.dubbo.application.protocol.accesslog=xxx # 网络传输方式 spring.dubbo.application.protocol.transporter=xxx # 信息交换方式 spring.dubbo.application.protocol.exchanger=xxx # 信息线程模型派发方式 spring.dubbo.application.protocol.dispatcher=xxx # 对称网络组网方式 spring.dubbo.application.protocol.networker=xxx # 服务器端实现 spring.dubbo.application.protocol.server=xxx # 客户端实现 spring.dubbo.application.protocol.client=xxx # 支持的telnet命令,多个命令用逗号分隔 spring.dubbo.application.protocol.telnet=xxx # 命令行提示符 spring.dubbo.application.protocol.prompt=xxx # status检查 spring.dubbo.application.protocol.status=xxx # 是否注册 spring.dubbo.application.protocol.status=xxx
2-4.@Service 服务提供者常用配置 常用 @Service 配置的如下
version 版本
group 分组
provider 提供者
protocol 服务协议
monitor 服务监控
registry 服务注册
…
2-5.@Reference 服务消费者常用配置 常用 @Reference 配置的如下
version 版本
group 分组
timeout 消费者调用提供者的超时时间
consumer 服务消费者
monitor 服务监控
registry 服务注册
2-6.总结 A) 依赖问题 目前测试的是Spring boot整合的dubbo依赖
1 2 3 4 5 <dependency> <groupId>io.dubbo.springboot</groupId> <artifactId>spring-boot-starter-dubbo</artifactId> <version>1.0.0</version> </dependency>
注意该依赖中存在了dubbo和zk所有依赖包,不需要额外的配置zk,否则会引起jar包冲突 ,选择时可以通过插件查看
注意提供者如果使用事务,那么需要导入AOP依赖
1 2 3 4 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
B)配置问题 注意ZK的注册地址
1 2 #ZK地址 spring.dubbo.registry.address=zookeeper://www.dony15.com:2181
注意服务包目录
1 spring.dubbo.scan=com.dony15.service
该目录的提供者和消费者并不需要一致,扫描的是本工程内对应的注解位置
1 2 3 4 5 #服务包目录-提供者 spring.dubbo.scan=com.dony15.service #服务包目录-消费者 spring.dubbo.scan=com.dony15.dubbo
C)注解问题 @Service(version = “1.0.0”)和@Reference(version = “1.0.0”)
版本号可以写也可以不写,但是这两个注解注意是dubbo包下,并非spring包
D)解耦 可以将通用的接口和实体类抽离出来打成jar进行依赖,优点
提高复用性
避免springboot的按需依赖原则 造成的过多引用问题
如果不这样做,注解方式的引用需要在提供者和消费者都建立服务接口,降低dubbo的实用性(每次调用服务都需要将服务内容复制一遍)
E)注解篇代码奉上Spring boot+Dubbo+ZK+JDBC+AOP+Mybatis+Restful https://github.com/dony15/my_springboot_code
###
3.整合Thymeleaf/Freemarker 3-1.代码 注解篇代码奉上Thymeleaf+Freemarker+Spring boot+Dubbo+ZK+JDBC+AOP+Mybatis+Restful https://github.com/dony15/my_springboot_code/tree/master/springboot-dubbo-mybatis-freemarker-thymeleaf
3-2.配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ... #Themleaf配置 spring.thymeleaf.content-type=text/html spring.thymeleaf.mode =LEGACYHTML5 #开发时关闭缓存,不然没法看到实时页面 spring.thymeleaf.cache=false #配置静态资源路径 spring.mvc.static-path-pattern=/static/** #DispatcherServlet 映射后缀(效果暂时没发现,并非伪静态技术) server.sevlet-path=*.html #freemarker模板 spring.freemarker.allow-request-override=false spring.freemarker.cache=false spring.freemarker.check-template-location=true spring.freemarker.charset=UTF-8 spring.freemarker.content-type=text/html spring.freemarker.expose-request-attributes=false spring.freemarker.expose-session-attributes=false spring.freemarker.expose-spring-macro-helpers=false
3-3.总结 Thymeleaf和Freemarker可以共存,当然语法有区别,根据实际业务选择吧,整合基本没有难点,有时间可以了解一下他们的配置文件都是啥意思,百度很多,不留了
A)严谨问题 Thymeleaf对标签格式要求比较严谨,如果需要可以通过依赖jar进行自动补充(前段不一定写的很完整哦)
1 2 3 4 5 6 <!--ThymeLeaf代码补全--> <dependency> <groupId>net.sourceforge.nekohtml</groupId> <artifactId>nekohtml</artifactId> <version>1.9.22</version> </dependency>
B)伪静态思路 通过过滤器实现伪静态化,优化SEO,项目在不值得做大量静态页面时,可以使用
一时半会没整合好,就注释掉了,大概这个思路( • ̀ω•́ )✧
1 2 3 4 5 6 <!--伪静态化优化方案,未实现--> <dependency> <groupId>org.tuckey</groupId> <artifactId>urlrewritefilter</artifactId> <version>4.0.4</version> </dependency>
C)依赖问题 Thymeleaf的依赖中已经存在spring-boot-starter-web的依赖
因此spring-boot-starter-web可以无情的去掉了
D)FreemarkerUtil使用问题 注意目录,如果写错了很尴尬哦.中间环节取不到值会提示异常,表达式值为null
E)前后端分离 如果你能参与项目的设计,那么通过/{page}进行较大程度的前后端分离,是个不错的点子哦,项目中有示例
4.整合 FastDFS+Nginx 本次整合会增加数据库字段,当然包括代码层的更新咯.url存图片来演示FastDFS功能
新增字段city_image存储url,演示是1张图,实际上在实体类中已经为多图扩展做了准备(数组切割)
4-1.代码 Thymeleaf+Freemarker+Spring boot+Dubbo+ZK+JDBC+AOP+Mybatis+Restful+FastDFS+Nginx
https://github.com/dony15/my_springboot_code/tree/master/3springboot-fastDFS-Nginx
4-2.配置文件 1 tracker_server=www.dony15.com:22122
不足
Nginx地址没有提出来,开发时应该提出来通用的,方案有很多,放在配置文件或者指定类或接口都可以
都没有做太多限制,比如图片类型,大小等等.可以进行各种判断过滤,这里只演示基础的功能实现
FastDFS只演示了增加,还缺少删和改哦,可以自行百度
4-3.总结 A)前段问题 这次演示没有使用富文本,可以更直接的去尝试细节(脑壳疼),
注意文件上传的类型,使用ajax的时候很容易前后不兼容,400或者406等
该方案是不依赖于form表单的ajax 详细见源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <input type="file" alt="插入图片" id="uploadFile" name="uploadFile" /> ----------------------------------------------------- $("#uploadFile" ).change(function ( ) { var imageForm = new FormData(); imageForm.append("uploadFile" , $("#uploadFile" ).get(0 ).files[0 ]); $.ajax({ type: 'POST' , url: "/insertImage" , data: imageForm, processData: false , contentType: false , success: function (data ) { var result = JSON .parse(data); if (result.error==1 ) { alert(result.message) }else { $("#image_echo" ).attr("src" , result.url); $("#cityImage" ).attr("value" , result.url); } }, error: function ( ) { alert("上传失败" ) } }); })
B)基础逻辑 基础的逻辑,文件上传到FastDFS,通过回调获取URI
再和Nginx的地址拼接成完整的URL响应给前段
前段拿到响应的URL和其他数据一起存进数据库
5.整合Redis 5-1.代码 Thymeleaf+Freemarker+Spring boot+Dubbo+ZK+JDBC+AOP+Mybatis+Restful+FastDFS+Nginx+Redis
https://github.com/dony15/my_springboot_code/tree/master/springboot4-redis
总结 A)依赖问题 注意redis需要和jackson一起,否则异常
1 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'functionDomainRedisTemplate' defined in class path resource [com/dony15/config/RedisConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.redis.core.RedisTemplate]: Factory method 'functionDomainRedisTemplate' threw exception; nested exception is java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ObjectMapper
我们可以看到这里的提示,缺少jacksion中的ObjectMapper
1 nested exception is java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ObjectMapper
因此依赖应该这样配套使用
1 2 3 4 5 6 7 8 9 10 <!-- 整合redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- jackson --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency>
当然,FastJSON也是需要的,主要用在处理业务的时候,选择怎么存的策略转换,比如List,Map,数组等的处理选择上
B)配置-单机版 Springboot整合redis,需要注意RedisTemplate的注入,通过配置类来实现
将RedisUtil注入封装RedisTemplate
调用RedisUtil通过自动装配或者@Resource注入即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #Redis #Matser的ip地址 redis.host=www.dony15.com #端口号 redis.port=6379 #如果有密码 redis.password= #客户端超时时间单位是毫秒 默认是2000 redis.timeout=20000 #最大空闲数 redis.maxIdle=300 #连接池的最大数据库连接数。设为0表示无限制,如果是jedis 2.4以后用redis.maxTotal #redis.maxActive=600 #控制一个pool可分配多少个jedis实例,用来替换上面的redis.maxActive,如果是jedis 2.4以后用该属性 redis.maxTotal=1000 #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。 redis.maxWaitMillis=10000 #连接的最小空闲时间 默认1800000毫秒(30分钟) redis.minEvictableIdleTimeMillis=300000 #每次释放连接的最大数目,默认3 redis.numTestsPerEvictionRun=1024 #逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 redis.timeBetweenEvictionRunsMillis=30000 #是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个 redis.testOnBorrow=true #在空闲时检查有效性, 默认false redis.testWhileIdle=true
6.整合Logback全局异常处理 个人理解目前Logback仍为主流的异常处理工具,但是配置细粒度 较为麻烦,并且难以分析,对于中小项目比较实用;
对于长期的中大型soa项目,建议添加Cat检测,更加细粒度控制大部分细节,使用也相对简单方便,如:
分布式细粒度实时监控
故障快速发现
系统问题分析
Cat报表展示消息类型
Transaction
Event
Heartbeat
Metric
Trace
各种埋点
丰富的模块警告通知/多种通知方式
…
Cat待更新,在新篇章介绍
6-1.代码 Thymeleaf+Freemarker+Spring boot+Dubbo+ZK+JDBC+AOP+Mybatis+Restful+FastDFS+Nginx+Redis+Logback
https://github.com/dony15/my_springboot_code/tree/master/springboot5-logback
6-2.配置 A)、Logger、appender及layout
Logger作为日志的记录器,把它关联到应用的对应的context上后,主要用于存放日志对象,也可以定义日志类型、级别。
Appender主要用于指定日志输出的目的地,目的地可以是控制台、文件、远程套接字服务器、 MySQL、 PostreSQL、 Oracle和其他数据库、 JMS和远程UNIX Syslog守护进程等。
Layout 负责把事件转换成字符串,格式化的日志信息的输出。
B)、logger context
各个logger 都被关联到一个 LoggerContext,LoggerContext负责制造logger,也负责以树结构排列各 logger。其他所有logger也通过org.slf4j.LoggerFactory 类的静态方法getLogger取得。 getLogger方法以 logger 名称为参数。用同一名字调用LoggerFactory.getLogger 方法所得到的永远都是同一个logger对象的引用。
C)、有效级别及级别的继承
Logger 可以被分配级别。级别包括:TRACE、DEBUG、INFO、WARN 和 ERROR,定义于 ch.qos.logback.classic.Level类。如果 logger没有被分配级别,那么它将从有被分配级别的最近的祖先那里继承级别。root logger 默认级别是 DEBUG。
D)、打印方法与基本的选择规则
打印方法决定记录请求的级别。例如,如果 L 是一个 logger 实例,那么,语句 L.info(“..”)是一条级别为 INFO 的记录语句。记录请求的级别在高于或等于其 logger 的有效级别时被称为被启用,否则,称为被禁用。记录请求级别为 p,其logger的有效级别为 q,只有则当 p>=q时,该请求才会被执行。
该规则是 logback 的核心。级别排序为: TRACE < DEBUG < INFO < WARN < ERROR 。
Logback默认配置的步骤
(1). 尝试在 classpath 下查找文件 logback-test.xml;
(2). 如果文件不存在,则查找文件 logback.xml;
(3). 如果两个文件都不存在,logback 用 Bas icConfigurator 自动对自己进行配置,这会导致记录输出到控制台。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 <?xml version="1.0" encoding="UTF-8"?> <configuration debug ="false" > <property name ="LOG_HOME" value ="D:/temp/log" /> <appender name ="STDOUT" class ="ch.qos.logback.core.ConsoleAppender" > <encoder class ="ch.qos.logback.classic.encoder.PatternLayoutEncoder" > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern > </encoder > </appender > <appender name ="FILE" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <rollingPolicy class ="ch.qos.logback.core.rolling.TimeBasedRollingPolicy" > <FileNamePattern > ${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern > <MaxHistory > 30</MaxHistory > </rollingPolicy > <encoder class ="ch.qos.logback.classic.encoder.PatternLayoutEncoder" > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern > </encoder > <triggeringPolicy class ="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy" > <MaxFileSize > 10MB</MaxFileSize > </triggeringPolicy > </appender > <logger name ="org.hibernate.type.descriptor.sql.BasicBinder" level ="TRACE" /> <logger name ="org.hibernate.type.descriptor.sql.BasicExtractor" level ="DEBUG" /> <logger name ="org.hibernate.SQL" level ="DEBUG" /> <logger name ="org.hibernate.engine.QueryParameters" level ="DEBUG" /> <logger name ="org.hibernate.engine.query.HQLQueryPlan" level ="DEBUG" /> <logger name ="com.apache.ibatis" level ="TRACE" /> <logger name ="java.sql.Connection" level ="DEBUG" /> <logger name ="java.sql.Statement" level ="DEBUG" /> <logger name ="java.sql.PreparedStatement" level ="DEBUG" /> <root level ="INFO" > <appender-ref ref ="STDOUT" /> <appender-ref ref ="FILE" /> </root > <appender name ="DB" class ="ch.qos.logback.classic.db.DBAppender" > <connectionSource class ="ch.qos.logback.core.db.DriverManagerConnectionSource" > <driverClass > com.mysql.jdbc.Driver</driverClass > <url > jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf-8&useSSL=false</url > <user > root</user > <password > 68835230</password > </connectionSource > </appender > <root level ="INFO" > <appender-ref ref ="DB" /> </root > </configuration >
6-3.建表语句 如果输出到数据库,那么需要先建表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 BEGIN ;DROP TABLE IF EXISTS logging_event_property;DROP TABLE IF EXISTS logging_event_exception;DROP TABLE IF EXISTS logging_event;COMMIT ; BEGIN ;CREATE TABLE logging_event ( timestmp BIGINT NOT NULL , formatted_message TEXT NOT NULL , logger_name VARCHAR (254 ) NOT NULL , level_string VARCHAR (254 ) NOT NULL , thread_name VARCHAR (254 ), reference_flag SMALLINT , arg0 VARCHAR (254 ), arg1 VARCHAR (254 ), arg2 VARCHAR (254 ), arg3 VARCHAR (254 ), caller_filename VARCHAR (254 ) NOT NULL , caller_class VARCHAR (254 ) NOT NULL , caller_method VARCHAR (254 ) NOT NULL , caller_line CHAR (4 ) NOT NULL , event_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY ); COMMIT ; BEGIN ;CREATE TABLE logging_event_property ( event_id BIGINT NOT NULL , mapped_key VARCHAR (254 ) NOT NULL , mapped_value TEXT , PRIMARY KEY (event_id, mapped_key), FOREIGN KEY (event_id) REFERENCES logging_event(event_id) ); COMMIT ; BEGIN ;CREATE TABLE logging_event_exception ( event_id BIGINT NOT NULL , i SMALLINT NOT NULL , trace_line VARCHAR (254 ) NOT NULL , PRIMARY KEY (event_id, i), FOREIGN KEY (event_id) REFERENCES logging_event(event_id) ); COMMIT ;
6-5.加入全局异常处理器即可使用 这里整合了ajax请求(需要ajax请求工具类)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package com.dony15.exception;import com.alibaba.fastjson.JSON;import com.dony15.utils.AjaxResponse;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.servlet.HandlerExceptionResolver;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;public class ExceptionHandler implements HandlerExceptionResolver { private Logger logger = LoggerFactory.getLogger(this .getClass()); @Override public ModelAndView resolveException (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { logger.error(ex.getMessage(),ex); if (request.getHeader("x-requested-with" ) != null && request.getHeader("x-requested-with" ).equalsIgnoreCase("XMLHttpRequest" )) { AjaxResponse ajaxResponse = new AjaxResponse(); ajaxResponse.setThrowable(ex); response.reset(); response.setContentType("application/json;charset=UTF-8" ); PrintWriter out = null ; try { out = response.getWriter(); } catch (IOException e) { logger.error(e.getMessage(),e); } out.print(JSON.toJSONString(ajaxResponse)); out.flush(); return null ; }else { ModelAndView mv = new ModelAndView(); Cookie[] cookies = request.getCookies(); if (cookies != null ) { for (int i = 0 ; i < cookies.length; i++) { if ("debug" .equals(cookies[i].getName())) { StackTraceElement[] stackTraces = ex.getStackTrace(); StringBuilder stackTraceStr = new StringBuilder(); stackTraceStr.append(ex.toString()) .append("<br>" ); for (int j = 0 ; j < stackTraces.length; j++) { stackTraceStr.append(stackTraces[j].toString()) .append("<br>" ); } mv.getModel().put("stackTraces" , stackTraceStr); } } } mv.setViewName("error.html" ); return mv; } } }
6-6.总结 A)依赖 依赖没有问题,spring-boot默认首先支持logback,不需要引入,详细关系可以查看关系图
B)配置 配置中需要注意的是,如果输出到数据库,那么除了配置文件外,需要在数据库中建好表哦
测试logback-test.xml
生产logback.xml
7.整合Quartz 7-1.配置组成 以下为Spring boot 方式的配置,不依赖于任何配置文件
JobFactory 配置类:Job注入到Spring中管理,否则无法与Spring中的Bean交互
QuartzConfig 配置类:Schedulder注入到Spring中管理
QuartzManager 工具管理类:封装调用Quartz的方法,如添加/修改/删除/查看
SelectCityJob Job类:任务类,处理业务逻辑的地方
注意此处配置的注入,实际上是将Job和Schedulder的工厂 交给Spring管理 , 因为Quartz本身具有自己的容器 ,而自身的容器和Spring的容器互相没有关联,导致Bean无法沟通
可参考该文章:
https://blog.csdn.net/xiaobuding007/article/details/80455187
①个人仓库中代码集成演示: https://github.com/dony15/my_springboot_code/tree/master/springboot6-quartz%20X 该演示是单独Quartz的集成,特意打穿MVC架构使用,问题和理解↓↓↓
7-2.原理 假设: 我们需要通过CMS界面来定时执行某个任务
① 中的集成实际上存在问题,首先我们知道Quartz底层调用Object.wait()
方法来阻塞实现
那么使用Quartz就必须异步实现,否则将会影响程序的正常运行
该演示中的集成恰好没有实现异步,我们一起来看后果是什么:
首先:如果我们在Controller调用Job的方法,那么会抛出TimeOut超时,其实很容易理解,
Object.wait()阻塞下是拿不到返回值的,该方法执行一段时间后,因为Controller中的请求本身有设定超时时间,
时间到了自然超时 (防止死锁和同步的问题,spring本身的优化方案)
超时情况下,我们的Quartz实际上是生效了的,而且会生效两次,这是SpringMVC本身的机制,超时时仍然会刷新该方法
这个时候的Quartz会被再次执行,导致一个无关紧要的异常提示:该job已经存在
1 2 3 4 5 6 7 8 9 10 public String getCityByName (String cityName) { Map<String, Object> params = new HashMap<>(); params.put("cityName" , cityName); quartzManager.addJob("getCityN" , "cityJ" , "getCityT" , "cityT" , SelectCityJob.class, "0/3 * * * * ?" , params); return "SUCCESS" ; }
解决这两个问题我进行了如下尝试
不调用需要返回值
提前进行任务判断
(Job中设置@DisallowConcurrentExecution 禁止并发)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public void getCityByName (String cityName) { Boolean exists = quartzManager.notExists("getCityT" , "cityT" ); Map<String, Object> params = new HashMap<>(); params.put("cityName" , cityName); if (!exists){ System.out.println("存在该任务哦,可以尝试移除" ); }else { quartzManager.addJob("getCityN" , "cityJ" , "getCityT" , "cityT" , SelectCityJob.class, "0/3 * * * * ?" , params); } }
实际上该方案只能解决第二个异常提示:该job已存在
对于SpringMVC 超时问题仍然无法解决,那么问题的根源又回到了一开始,为什么Quartz要使用异步来处理
我们知道SpringMVC本身有一套完善的请求流程(13步),但是我们在handler中的业务操作同步阻塞,
导致后续的流程需要超时后才能执行
虽然以上两个异常提示都不会终止程序,也不会终止业务的实现,但是等待超时的时间和毫无意义的异常仍然不利于我们的开发
同理,在此代码中,Quartz和dubbo 也会发生同步超时的状态
因此,我们可以重新屡一下
我们需要定时做些事情–>选择定时框架Quartz(扔进大后方)–>Controller难以做到异步通知(X)–>选择MQ消息队列通知(√)–>完美ヾ(゚∀゚ゞ)
7-3.正确代码使用 修改后的完整代码
Thymeleaf+Freemarker+Spring boot+Dubbo+ZK+JDBC+AOP+Mybatis+Restful+FastDFS+Nginx+Redis+Logback+Quartz+ActiveMQ
https://github.com/dony15/my_springboot_code/tree/master/springboot7-quartz%20%E2%88%9A-ActiveMQ
加入MQ
ActiveMQ
注意:序列化的对象不可以直接用来做ActiveMQ的消息,需要添加配置允许
1 2 3 4 spring.activemq.packages.trust-all=true //允许所有 此处百度 //允许指定序列化类 详细可参考ActiveMQ文章
核心:
ActiveMQ配置文件
ActiveProvider
ActiveConsumer
7-4.结合数据库 本次数据库结合并非持久化MQ消息,只是持久化Quartz的任务
假装结合一下,剩下的就是普通的业务逻辑了,说明书代替就好
7-5.ActiveMQ消息持久化到数据库 http://topmanopensource.iteye.com/blog/1066383
7-6.RocketMQ 资源传送门
https://blog.csdn.net/zhangll_2008/article/details/78657177
在MQ文章中记录个人总结
https://dony15.github.io/2018/08/24/MQ%E5%9F%BA%E7%A1%80%E4%B8%8E%E8%BF%90%E7%94%A8/
7-7.整合Quartz总结 A)结构问题 注意其本身需要异步实现,否则基本上会出现超时问题
本身具有自己的容器,该容器与Spring容器无关,需要配置注入到Spring管理,才能使用Spring中的bean
B)位置 建议放在大后方,还是异步处理的问题,最好不要与其他框架同步